﻿using System;
using System.Collections.Generic;
using System.Linq;
using OverStore.Configuration.ComponentModel;
using OverStore.Configuration.Storage;
using OverStore.Runtime.Logger;
using OverStore.Runtime.PersistenceServiceProvider;
using OverStore.Runtime.ObjectRepository.ChangesTracker;
using OverStore.Runtime.Storage;
using OverStore.Sql;
using OverStore.Sql.TableSchema;
using OverStore.Runtime.ObjectRepository.Depot.IdentifierGenerator;
using OverStore.Runtime.ObjectRepository.Depot;
using OverStore.Runtime.ObjectRepository.IdentifierProvider;
using OverStore.Runtime.ObjectRepository.Mapping;
using OverStore.Runtime.ObjectRepository.CommandSet;
using OverStore.Runtime.ObjectRepository.Accessors;
using OverStore.Configuration.ObjectRepository.CommandSet;
using OverStore.Configuration.ObjectRepository.CallbackManager;
using OverStore.Configuration.ObjectRepository;
using OverStore.Configuration.ObjectRepository.Depot.IdentifierGenerator;
using OverStore.Configuration.ObjectRepository.Accessors;
using OverStore.Configuration.ObjectRepository.Depot;
using OverStore.Configuration.ObjectRepository.ChangesTracker;
using OverStore.Configuration.ObjectRepository.IdentifierProvider;
using OverStore.Configuration.ObjectRepository.Mapping;

namespace OverStore.Configuration.Components
{
    /// <summary>
    /// Contains methods for creating different configuration elements.
    /// </summary>
    public static class Component
    {
        /// <summary>
        /// Exposes set of static methods allows to create different types of ADO.NET primitives.
        /// </summary>
        public static class AdoCommand
        {
            /// <summary>
            /// Creates new command intented for read data from storage.
            /// </summary>
            /// <param name="sql">The SQL instruction executed by command.</param>
            /// <returns>The new instance of <see cref="AdoNetCommandConfiguration"/> class.</returns>
            public static AdoNetCommandConfiguration ReadCommand(string sql)
            {
                if (String.IsNullOrEmpty(sql))
                    throw new ArgumentNullException("sql");
                return new AdoNetCommandConfiguration
                {
                    CommandType = System.Data.CommandType.Text,
                    Sql = sql,
                    ResultType = AdoCommandResultType.DataReaderResultSet
                };
            }

            /// <summary>
            /// Creates new command intended for write data to storage.
            /// </summary>
            /// <param name="sql">The SQL instruction used for write data to storage.</param>
            /// <param name="resultType">Define how the command outputs it result.</param>
            /// <returns>The new instance of <see cref="AdoNetCommandConfiguration"/> class.</returns>
            public static AdoNetCommandConfiguration WriteCommand(string sql, AdoCommandResultType resultType)
            {
                if (String.IsNullOrEmpty(sql))
                    throw new ArgumentNullException("sql");
                return new AdoNetCommandConfiguration
                {
                    Sql = sql,
                    ResultType = resultType,
                    CommandType = System.Data.CommandType.Text
                };
            }

            /// <summary>
            /// Creates new command intended for write data to storage which returns it result via output parameters.
            /// </summary>
            /// <param name="sql">The SQL instruction used for write data to storage.</param>
            /// <returns>The new instance of <see cref="AdoNetCommandConfiguration"/> class.</returns>
            public static AdoNetCommandConfiguration WriteCommand(string sql)
            {
                return WriteCommand(sql, AdoCommandResultType.OutputParameters);
            }
        }

        /// <summary>
        /// Contains set of methods allows creating of different types of logger.
        /// </summary>
        public static class Logger
        {
            /// <summary>
            /// Creates new instance of logger outputs information to console.
            /// </summary>
            /// <returns>The instance of <see cref="ILogger"/> that outputs diagnostics information to standard output.</returns>
            public static IComponentConfiguration<ILogger> ToConsole()
            {
                return SingletonConfiguration<ILogger, ConsoleLogger>.Value;
            }

            /// <summary>
            /// Creates configuration for logger outputs information to Debug output.
            /// </summary>
            public static IComponentConfiguration<ILogger> Debug()
            {
                return SingletonConfiguration<ILogger, DebugLogger>.Value;
            }

            /// <summary>
            /// Creates new instance of logger that does not outputs any information.
            /// </summary>
            public static IComponentConfiguration<ILogger> Dummy()
            {
                return SingletonConfiguration<ILogger, DummyLogger>.Value;
            }
        }

        /// <summary>
        /// Exposes set of extension methods allows to create different variant of command set configurations.
        /// </summary>
        public static class CommandSet
        {
            /// <summary>
            /// Creates <see cref="ICommandSet"/> configuration based on specified table scema and <see cref="SqlFormatting"/>.
            /// </summary>
            public static TableCommandSetConfiguration FromTable(Table table, bool performDeleteAsUpdate)
            {
                if (table == null)
                    throw new ArgumentNullException("table");
                return new TableCommandSetConfiguration
                {
                    PerformDeleteAsUpdate = performDeleteAsUpdate,
                    TableSchema = table
                };
            }
        }

        /// <summary>
        /// Exposes set of extension methods allows to create different types of identifier generators.
        /// </summary>
        public static class IdentifierGenerator
        {
            /// <summary>
            /// Creates identifier generator generates new Guids.
            /// </summary>
            /// <typeparam name="TInstance">The type of the instance.</typeparam>
            /// <returns>The new instance of <see cref="GuidGenerator{TInstance}"/> configuration.</returns>
            public static IComponentConfiguration<IIdentifierGenerator<TInstance, Guid>> Guid<TInstance>()
                where TInstance : class
            {
                return SingletonConfiguration<IIdentifierGenerator<TInstance, Guid>, GuidGenerator<TInstance>>.Value;
            }

            /// <summary>
            /// Creates identifier generator that generates sequence of integers using specified starting result and 
            /// increment.
            /// </summary>
            /// <typeparam name="TInstance">The type of the instance.</typeparam>
            /// <param name="startingValue">The starting result.</param>
            /// <param name="increment">The increment.</param>
            /// <returns>The new instance of <see cref="SequentialGeneratorConfiguration{TInstance}"/> class.</returns>
            /// <exception cref="T:System.ArgumentException"><paramref name="increment"/> equals to zero.</exception>
            public static IComponentConfiguration<IIdentifierGenerator<TInstance, int>> Seq<TInstance>(int startingValue, int increment)
                where TInstance : class
            {
                if (increment == 0)
                    throw new ArgumentException("Increment should be different than zero.");
                return new SequentialGeneratorConfiguration<TInstance>
                {
                    StartingValue = startingValue,
                    Increment = increment
                };
            }

            /// <summary>
            /// Creates new insance of identifier generator uses user-defined delegate for 
            /// create generate next identifier.
            /// </summary>
            /// <typeparam name="TInstance">The type of the instance.</typeparam>
            /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
            /// <param name="nextIdentifier">Delegate uses for generate next identifier.</param>
            /// <param name="startingValue">The starting identifier result.</param>
            /// <returns>The new instance of <see cref="DelegateGeneratorConfiguration{TInstance, TIdentifier}"/>.</returns>
            public static IComponentConfiguration<IIdentifierGenerator<TInstance, TIdentifier>> Delegate<TInstance, TIdentifier>(
                Func<TInstance, TIdentifier, TIdentifier> nextIdentifier, TIdentifier startingValue)
                where TInstance : class
            {
                if (nextIdentifier == null)
                    throw new ArgumentNullException("nextIdentifier");
                return new DelegateGeneratorConfiguration<TInstance, TIdentifier>
                {
                    NextIdentifierFunc = nextIdentifier,
                    StartingValue = startingValue
                };
            }

            /// <summary>
            /// Creates new insance of identifier generator uses user-defined delegate for
            /// create generate next identifier.
            /// </summary>
            /// <typeparam name="TInstance">The type of the instance.</typeparam>
            /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
            /// <param name="nextIdentifier">Delegate uses for generate next identifier.</param>
            /// <returns>
            /// The new instance of <see cref="DelegateGeneratorConfiguration{TInstance, TIdentifier}"/>.
            /// </returns>
            public static IComponentConfiguration<IIdentifierGenerator<TInstance, TIdentifier>> Delegate<TInstance, TIdentifier>(Func<TInstance, TIdentifier, TIdentifier> nextIdentifier)
                where TInstance : class
            {
                if (nextIdentifier == null)
                    throw new ArgumentNullException("nextIdentifier");
                return new DelegateGeneratorConfiguration<TInstance, TIdentifier>
                {
                    NextIdentifierFunc = nextIdentifier
                };
            }

            /// <summary>
            /// Creates new identifier generator that gets identifier from data of new instance.
            /// It should be used if instance generates new identifier by itself.
            /// </summary>
            /// <typeparam name="TInstance">The type of the instance.</typeparam>
            /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
            /// <param name="propertyName">Name of the property contains instance identifier.</param>
            /// <returns>New instance of identifier generator.</returns>
            public static IComponentConfiguration<IIdentifierGenerator<TInstance, TIdentifier>> InstanceGeneratedIdentifiers<TInstance, TIdentifier>(string propertyName)
                where TInstance : class
            {
                if (String.IsNullOrEmpty(propertyName))
                    throw new ArgumentNullException("propertyName");
                var accessor = new NextIdAccessor<TInstance, TIdentifier>(new PropertyAccessor<TInstance, TIdentifier>(propertyName));
                return new DelegateGeneratorConfiguration<TInstance, TIdentifier>
                {
                    NextIdentifierFunc = accessor.GetIdentifier
                };
            }

            /// <summary>
            /// Creates new identifier generator that gets identifier from data of new instance.
            /// It should be used if instance generates new identifier by itself.
            /// </summary>
            /// <typeparam name="TInstance">The type of the instance.</typeparam>
            /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
            /// <param name="identifierAccessor">The identifier accessor uses for access identifier of specified instance.</param>
            /// <returns>New instance of identifier generator.</returns>
            public static IComponentConfiguration<IIdentifierGenerator<TInstance, TIdentifier>> InstanceGeneratedIdentifiers<TInstance, TIdentifier>(ITypedAccessor<TInstance, TIdentifier> identifierAccessor)
                where TInstance : class
            {
                if (identifierAccessor == null)
                    throw new ArgumentNullException("identifierAccessor");
                var accessor = new NextIdAccessor<TInstance, TIdentifier>(identifierAccessor);
                return new DelegateGeneratorConfiguration<TInstance, TIdentifier>
                {
                    NextIdentifierFunc = accessor.GetIdentifier
                };
            }

            private sealed class NextIdAccessor<TInstance, TIdentifier>
                where TInstance : class
            {
                private readonly ITypedAccessor<TInstance, TIdentifier> _IdentifierAccessor;

                public NextIdAccessor(ITypedAccessor<TInstance, TIdentifier> identifierAccessor)
                {
                    _IdentifierAccessor = identifierAccessor;
                }

                public TIdentifier GetIdentifier(TInstance instance, TIdentifier unused)
                {
                    return _IdentifierAccessor.GetValue(instance);
                }
            }
        }

        /// <summary>
        /// Exposes set of extension methods allows to create different types of object depot.
        /// </summary>
        public static class Depot
        {
            /// <summary>
            /// Create object depot for objects that uses own data (f.e. property or field)
            /// as object identifier. Object should generate unique key by itself.
            /// </summary>
            /// <typeparam name="TInstance">OverrideDbType of object for depot.</typeparam>
            /// <typeparam name="TIdentifier">OverrideDbType of identifier.</typeparam>
            /// <param name="getKeyFunc">Return key of specified identifier.</param>
            /// <param name="createInstance">Creates new instance and apply specified identifier.</param>
            /// <returns></returns>
            public static IComponentConfiguration<IDepot<TInstance, TIdentifier>> WithInternalKey<TInstance, TIdentifier>(
                Func<TInstance, TIdentifier> getKeyFunc,
                Func<TInstance> createInstance)
                where TInstance : class
            {
                if (getKeyFunc == null)
                    throw new ArgumentNullException("getKeyFunc");
                if (createInstance == null)
                    throw new ArgumentNullException("createInstance");
                return new DelegateDepotConfiguration<TInstance, TIdentifier>
                {
                    CreateInstanceFunction = createInstance,
                    IdentifierGenerator = IdentifierGenerator.InstanceGeneratedIdentifiers(new ExpressionAccessor<TInstance, TIdentifier>(
                        getKeyFunc, (_inst, _val) => { }))
                };
            }

            /// <summary>
            /// Create object depot for objects that uses property with <paramref name="propertyName"/> name
            /// as object identifier. Object should generate unique key by itself.
            /// </summary>
            /// <typeparam name="TInstance">OverrideDbType of object for depot.</typeparam>
            /// <typeparam name="TIdentifier">OverrideDbType of identifier.</typeparam>
            /// <param name="propertyName">Name of property contains identifier for object.</param>
            public static IComponentConfiguration<IDepot<TInstance, TIdentifier>> WithInternalKey<TInstance, TIdentifier>(string propertyName) where TInstance : class
            {
                if (String.IsNullOrEmpty(propertyName))
                    throw new ArgumentNullException("propertyName");
                var accessor = new PropertyAccessor<TInstance, TIdentifier>(propertyName);
                return new DelegateDepotConfiguration<TInstance, TIdentifier>
                {
                    StoreIdentifierCopy = accessor.SetValue,
                    CreateInstanceFunction = Activator.CreateInstance<TInstance>,
                    IdentifierGenerator = IdentifierGenerator.InstanceGeneratedIdentifiers(accessor),
                };
            }

            /// <summary>
            /// Creates depot with attached autogenerated integer identifiers.
            /// </summary>
            public static IComponentConfiguration<IDepot<TInstance, int>> Auto<TInstance>()
                where TInstance : class
            {
                return new DelegateDepotConfiguration<TInstance, int>
                {
                    StoreIdentifierCopy = (o, i) => { },
                    CreateInstanceFunction = Activator.CreateInstance<TInstance>,
                    IdentifierGenerator = new SequentialGeneratorConfiguration<TInstance>(),
                };
            }

            /// <summary>
            /// Create object depot using specified delegates for creating instances, applying identifier to instance,
            /// and with specified object that generates identifiers for new instances.
            /// </summary>
            /// <typeparam name="TInstance">The type of the instance.</typeparam>
            /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
            /// <param name="createInstance">Delegate for create new instance of <typeparamref name="TInstance"/> class.</param>
            /// <param name="applyIdentifierAction">Delegate used when actual identifier of specified instance is changed and needs to be
            /// applied to instance.</param>
            /// <param name="idGenerator">The object generates sequence of <typeparamref name="TIdentifier"/> values that are used as identifiers for
            /// new instances.</param>
            /// <returns></returns>
            public static IComponentConfiguration<IDepot<TInstance, TIdentifier>> Delegate<TInstance, TIdentifier>(
                Func<TInstance> createInstance,
                Action<TInstance, TIdentifier> applyIdentifierAction,
                IComponentConfiguration<IIdentifierGenerator<TInstance, TIdentifier>> idGenerator)
                where TInstance : class
            {
                if (createInstance == null)
                    throw new ArgumentNullException("createInstance");
                if (applyIdentifierAction == null)
                    throw new ArgumentNullException("applyIdentifierAction");
                if (idGenerator == null)
                    throw new ArgumentNullException("idGenerator");
                return new DelegateDepotConfiguration<TInstance, TIdentifier>
                {
                    CreateInstanceFunction = createInstance,
                    StoreIdentifierCopy = applyIdentifierAction,
                    IdentifierGenerator = idGenerator,
                };
            }
        }

        /// <summary>
        /// Exposes set of extension methods allows to create different types of changes tracker.
        /// </summary>
        public static class InstanceChangesTracker
        {
            /// <summary>
            /// Creates <see cref="ChecksumObjectChangesTrackerConfiguration{TInstance,TIdentifier}"/> that uses 
            /// specified set of columns to define changes.
            /// </summary>
            public static IComponentConfiguration<IChangesTracker<TInstance>> Checksum<TInstance, TIdentifier>(params string[] columns)
                where TInstance : class
            {
                if (columns == null || columns.Length == 0 || columns.Any(c => c == null))
                    throw new ArgumentNullException("columns");
                return new ChecksumObjectChangesTrackerConfiguration<TInstance, TIdentifier>()
                           {
                               Columns = columns
                           };
            }

            /// <summary>
            /// Creates new instance of object changes tracker uses specified delegate for get object state.
            /// </summary>
            public static IComponentConfiguration<IChangesTracker<TInstance>> Delegate<TInstance, TIdentifier, TState>(
                Func<TInstance, TState> getStateFunc)
                where TInstance : class
                where TState : IEquatable<TState>
            {
                if (getStateFunc == null)
                    throw new ArgumentNullException("getStateFunc");
                return new DelegateChangesTrackerConfiguration<TInstance, TIdentifier, TState> { GetStateFunc = getStateFunc };
            }
        }

        /// <summary>
        /// Exposes set of static methods allows to create different types of identifier providers.
        /// </summary>
        public static class IdentifierProvider
        {
            /// <summary>
            /// Creates configuration for component that extracts identifier from data record.
            /// </summary>
            /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
            /// <param name="provideIdentifier">The provide identifier.</param>
            /// <returns>New instance of identifier provider configuration.</returns>
            public static IComponentConfiguration<IIdentifierProvider<TIdentifier>> Delegate<TIdentifier>(Func<TypedRecord, TIdentifier> provideIdentifier)
            {
                if (Object.ReferenceEquals(provideIdentifier, null))
                    throw new ArgumentNullException("provideIdentifier");
                return new DelegateIdentifierProviderConfiguration<TIdentifier> { ProvideIdentifier = provideIdentifier };
            }
        }

        /// <summary>
        /// Exposes set of static methods helps to create callback manager.
        /// </summary>
        public static class CallbackManager
        {
            /// <summary>
            /// Creates new instance of callback manager uses user-defined delegates to performs callback.
            /// Use set of methods exposes by return value to fluent configuration of callback manager.
            /// </summary>
            public static DelegateCallbackManagerConfiguration<TInstance> Delegate<TInstance>()
                where TInstance : class
            {
                return new DelegateCallbackManagerConfiguration<TInstance>();
            }
        }

        /// <summary>
        /// Provide set of extension methods allows to create configurations for <see cref="IMapper{TInstance}"/> component.
        /// </summary>
        public static class Mapper
        {
            /// <summary>
            /// Creates new instance of Mapper for <typeparamref name="TInstance"/> type uses 
            /// user-defined delegates for get data of instance and apply data to instance.
            /// </summary>
            public static IComponentConfiguration<IMapper<TInstance>> Delegate<TInstance>(
                Func<TInstance, string, IRegistry, object> getObjectData,
                Action<TypedRecord, TInstance, IRegistry> applyObjectData)
                where TInstance : class
            {
                if (getObjectData == null)
                    throw new ArgumentNullException("getObjectData");
                if (applyObjectData == null)
                    throw new ArgumentNullException("applyObjectData");
                return new DelegateMapperConfiguration<TInstance>
                {
                    GetObjectDataFunction = getObjectData,
                    ApplyObjectDataFunction = applyObjectData
                };
            }

            /// <summary>
            /// Creates configuration for <see cref="IMapper{TInstance}"/> implementation that uses <typeparamref name="TInstance"/>
            /// metainformation for mapping. Reflection mapper has some restriction: it only maps primitive type, not entity references, 
            /// it can't map object keys. Use reflection mapper to map parameters for object readers.
            /// Names in resultset matches property names in <typeparamref name="TInstace"/> type.
            /// </summary>
            public static IComponentConfiguration<IMapper<TInstance>> Reflection<TInstance>()
                where TInstance : class
            {
                return SingletonConfiguration<IMapper<TInstance>, ReflectionMapper<TInstance>>.Value;
            }
        }

        /// <summary>
        /// Creates new <see cref="Table"/> using specified name and
        /// set of columns.
        /// </summary>
        public static Table CreateTable(string name)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            return new Table
            {
                Name = name
            };
        }

        /// <summary>
        /// Creates object repository configured as mappings class to table.
        /// </summary>
        public static TableMappingRepositoryConfiguration<TInstance, TIdentifier> MapClassToTable<TInstance, TIdentifier>(
            string tableName)
            where TInstance : class
        {
            if (String.IsNullOrEmpty(tableName))
                throw new ArgumentNullException("tableName");
            return new TableMappingRepositoryConfiguration<TInstance, TIdentifier>
            {
                Table = tableName
            };
        }
    }
}
